/**
 * This file is part of 1genia trampoline
 * Copyright (C) 2007 1genia (contact@1genia.com)
 *
 * This library is free software; you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; version 3 of the License. 
 *
 * This library is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Library General Public License for more details. 
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; see the file COPYING.TXT.  If not,
 * write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301, USA. 
 **/
package com.genia.toolbox.persistence.memory.manager;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.genia.toolbox.basics.exception.BundledException;
import com.genia.toolbox.persistence.basis.manager.AbstractPersistenceManager;
import com.genia.toolbox.persistence.bean.Persistable;
import com.genia.toolbox.persistence.bean.QueryParameters;
import com.genia.toolbox.persistence.criteria.Criteria;
import com.genia.toolbox.persistence.criteria.CriteriaResult;
import com.genia.toolbox.persistence.exception.ObjectIsNotPersistentException;
import com.genia.toolbox.persistence.exception.ObjectIsPersistentException;
import com.genia.toolbox.persistence.exception.PersistenceException;
import com.genia.toolbox.persistence.manager.FieldManager;
import com.genia.toolbox.persistence.manager.OrderManager;
import com.genia.toolbox.persistence.manager.RestrictionsManager;
import com.genia.toolbox.persistence.memory.criteria.MemoryCriteria;

/**
 * Memory implementation of the persistent manager for saving, updating or
 * deleting an instance of persistable object.
 */
public class MemoryPersistenceManager
    extends AbstractPersistenceManager
{

  /**
   * the map containing all persistent objects.
   */
  private final transient Map<Class<?>, Map<Serializable, Persistable<?>>> objects = new HashMap<Class<?>, Map<Serializable, Persistable<?>>>();

  /**
   * the <code>FieldManager</code> to use with this implementation.
   */
  private MemoryFieldManager fieldManager;

  /**
   * the <code>OrderManager</code> to use with this implementation.
   */
  private MemoryOrderManager orderManager;

  /**
   * the <code>RestrictionsManager</code> to use with this implementation.
   */
  private MemoryRestrictionsManager restrictionsManager;



  /**
   * This method deletes an object.
   * 
   * @param object
   *          The object to delete
   * @throws PersistenceException
   *           when an errors occur.
   */
  @SuppressWarnings("unchecked")
  public void delete(final Persistable<? extends Serializable> object)
      throws PersistenceException
  {
    if (get(object.getClass(), object.getIdentifier()) == null) {
      return;
    }
    try {
      getDependencyManager().onDelete(object);
    }
    catch (final BundledException e) {
      throw convertException(e);
    }
    final Map<Serializable, Persistable<?>> objectsByClass = objects.get(object.getClass());
    if (objectsByClass != null) {
      objectsByClass.remove(object.getIdentifier());
    }
  }



  /**
   * This method returns the results of a criteria.
   * 
   * @param <MAINTYPE>
   *          the main type of the criteria.
   * @param criteria
   *          the criteria that describes the query
   * @param parameters
   *          the parameters of this search
   * @return the list of <code>CriteriaResult</code> that represents the
   *         result of the query
   * @throws PersistenceException
   *           when a persistence error occurred
   */
  @SuppressWarnings("unchecked")
  public <MAINTYPE> List<CriteriaResult<MAINTYPE>> find(final Criteria<MAINTYPE> criteria, QueryParameters parameters)
      throws PersistenceException
  {
    final List res = ((MemoryCriteria) criteria).list();
    int fromIndex = 0;
    int toIndex = res.size();
    if (parameters.getFirstResult() != null) {
      fromIndex = parameters.getFirstResult();
    }
    if (parameters.getMaxResults() != null && toIndex > fromIndex + parameters.getMaxResults()) {
      toIndex = fromIndex + parameters.getMaxResults();
    }
    return res.subList(fromIndex, toIndex);
  }



  /**
   * This method get an persited object given its class and identifier.
   * 
   * @param <IDENTIFIER>
   *          The type of the identifier
   * @param <TYPE>
   *          The type of the object to retrieve
   * @param type
   *          The class of the object to retrieve
   * @param identifier
   *          The identifier of the object to retrieve
   * @return the object of class <code>type</code> with the identifier
   *         <code>identifier</code> or <code>null</code> if no such object
   *         exists
   */
  public <IDENTIFIER extends Serializable, TYPE extends Persistable<IDENTIFIER>> TYPE get(final Class<TYPE> type, final IDENTIFIER identifier)
  {
    for (final Class<?> existingType : objects.keySet()) {
      if (existingType.isAssignableFrom(type)) {
        @SuppressWarnings("unchecked")
        final TYPE res = (TYPE) objects.get(existingType).get(identifier);
        if (res != null) {
          return res;
        }
      }
    }
    return null;
  }



  /**
   * This methods return all the persisted objects of a given type.
   * 
   * @param <TYPE>
   *          the type of the objects to retrieve
   * @param type
   *          the class of the objects to retrieve
   * @return the list of all objects of the given tyoe
   */
  @SuppressWarnings("unchecked")
  public <TYPE> List<TYPE> getAll(final Class<TYPE> type)
  {
    final List<TYPE> res = new ArrayList<TYPE>();
    for (final Class<?> existingType : objects.keySet()) {
      if (existingType.isAssignableFrom(type)) {
        res.addAll((Collection<? extends TYPE>) objects.get(existingType).values());
      }
    }
    return res;

  }



  /**
   * This methods return a new <code>Criteria</code> for the object of type
   * <code>mainType</code>.
   * 
   * @param <MAINTYPE>
   *          The main type of the criteria
   * @param mainType
   *          The class of the main type of the criteria
   * @return a <code>Criteria</code> to describe a new query
   */
  public <MAINTYPE> Criteria<MAINTYPE> getCriteria(final Class<MAINTYPE> mainType)
  {
    return new MemoryCriteria<MAINTYPE>(this, mainType);
  }



  /**
   * getter for the fieldManager property.
   * 
   * @return the fieldManager
   */
  public FieldManager getFieldManager()
  {
    return fieldManager;
  }



  /**
   * getter for the orderManager property.
   * 
   * @return the orderManager
   */
  public OrderManager getOrderManager()
  {
    return orderManager;
  }



  /**
   * getter for the restrictionsManager property.
   * 
   * @return the restrictionsManager
   */
  public RestrictionsManager getRestrictionsManager()
  {
    return restrictionsManager;
  }



  /**
   * This method saves a new object.
   * 
   * @param object
   *          The object to save
   * @throws PersistenceException
   *           when an errors occur.
   */
  public void save(final Persistable<? extends Serializable> object)
      throws PersistenceException
  {
    Map<Serializable, Persistable<?>> objectsByClass = objects.get(object.getClass());
    if (objectsByClass == null) {
      objectsByClass = new HashMap<Serializable, Persistable<?>>();
      objects.put(object.getClass(), objectsByClass);
    }
    if (objectsByClass.get(object.getIdentifier()) != null) {
      throw new ObjectIsPersistentException();
    }
    try {
      getDependencyManager().onCreate(object);
      objectsByClass.put(object.getIdentifier(), object);
      getDependencyManager().onPostCreate(object);
    }
    catch (final BundledException e) {
      throw convertException(e);
    }
  }



  /**
   * This method saves a new object or update an already persistent object. This
   * method should be avoided as much as possible. Finding if an object is
   * persistent or not is tedious and error prone, unexpected comportment can
   * arrise.
   * 
   * @param object
   *          The object to save or update
   * @throws PersistenceException
   *           if any problems occur during persistence process
   * @see com.genia.toolbox.persistence.manager.PersistenceManager#saveOrUpdate(com.genia.toolbox.persistence.bean.Persistable)
   */
  @SuppressWarnings("unchecked")
  public void saveOrUpdate(Persistable<? extends Serializable> object)
      throws PersistenceException
  {
    if (object.getIdentifier() != null && get(object.getClass(), object.getIdentifier()) != null) {
      update(object);
    }
    else {
      save(object);
    }
  }



  /**
   * setter for the fieldManager property.
   * 
   * @param fieldManager
   *          the fieldManager to set
   */
  public void setFieldManager(final FieldManager fieldManager)
  {
    this.fieldManager = (MemoryFieldManager) fieldManager;
  }



  /**
   * setter for the orderManager property.
   * 
   * @param orderManager
   *          the orderManager to set
   */
  public void setOrderManager(final OrderManager orderManager)
  {
    this.orderManager = (MemoryOrderManager) orderManager;
  }



  /**
   * setter for the restrictionsManager property.
   * 
   * @param restrictionsManager
   *          the restrictionsManager to set
   */
  public void setRestrictionsManager(final RestrictionsManager restrictionsManager)
  {
    this.restrictionsManager = (MemoryRestrictionsManager) restrictionsManager;
  }



  /**
   * This method updates an object.
   * 
   * @param object
   *          The object to update
   * @throws PersistenceException
   *           when an errors occur.
   */
  @SuppressWarnings("unchecked")
  public void update(final Persistable<? extends Serializable> object)
      throws PersistenceException
  {
    final Persistable persistantObject = get(object.getClass(), object.getIdentifier());
    if (!object.equals(persistantObject)) {
      throw new ObjectIsNotPersistentException();
    }
    try {
      getDependencyManager().onUpdate(object);
      getDependencyManager().onPostUpdate(object);
    }
    catch (final BundledException e) {
      throw convertException(e);
    }
    // Do Nothing, this implementation does modify objects in real time.
  }

}
